package com.jf.anmocker.plugin.mockcore.asm

import org.objectweb.asm.*
import org.objectweb.asm.tree.ClassNode

class AnMockerClzNode(val api : Int = Opcodes.ASM9, val nextVisitor : ClassVisitor) : ClassNode(api){

    private var needSaveToPool = false
    private var classMocker : AnMockerClzNode? = null
    private var methodMockers = ArrayList<AnMockerMethodNode>()
    private var isClassMocker = false
    private var targetClass : String? = null

    init {
        this.cv = nextVisitor
    }

    override fun visit(
        version: Int,
        access: Int,
        name: String?,
        signature: String?,
        superName: String?,
        interfaces: Array<out String>?
    ) {
        //println("version:$version signature:$signature interfaces:$interfaces")
        if (AnMockerFilter.isInterface(access)) {
            println("$name is interface")
        }else if (AnMockerFilter.isEnum(access)) {
            println("$name is enum")
        } else {
            // is not an interface
            //saveClassToPool(name)
            name?.let {
                //获取缓存中的classMocker用于替换操作
                if(AsmMockerCache.getInstance().isTargetClass(it)){
                    AsmMockerCache.getInstance().getClassMocker(it)?.let { mocker ->
                        if(mocker is AnMockerClzNode){
                            classMocker = mocker //mocker信息持有者
                        }else{
                            println("$name mocker is not AnMockerClzNode")
                        }
                    }
                //不是mocker目标则存入解析类缓存池中
                }else{
                    needSaveToPool = true
                }
            }
        }
        super.visit(version, access, name, signature, superName, interfaces)
    }

    override fun visitTypeAnnotation(
        typeRef: Int,
        typePath: TypePath?,
        descriptor: String?,
        visible: Boolean
    ): AnnotationVisitor {
        //println("AnMockerClzNode visitTypeAnnotation descriptor = $descriptor")
        return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible)
    }

    override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
        //println("AnMockerClzNode visitAnnotation descriptor = $descriptor")
        val annotation = ClassAnoNode(descriptor, api).apply {
            setVisitListener(object : ClassAnoVisitListener{
                override fun classVisited(isMocker: Boolean, targetClass: String?) {
                    this@AnMockerClzNode.isClassMocker = isMocker
                    this@AnMockerClzNode.targetClass = targetClass
                }
            })
        }
        if (visible) {
            visibleAnnotations = visibleAnnotations ?: ArrayList()
            visibleAnnotations.add(annotation)
        } else {
            invisibleAnnotations = invisibleAnnotations ?: ArrayList()
            invisibleAnnotations.add(annotation)
        }
        return annotation
    }

    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        val methodNode = AnMockerMethodNode(api, access, name, descriptor, signature, exceptions)
        //注入mock数据引用
        methodNode.injectMockerInfo(classMocker)
        //存入classNode的methods里面
        methods.add(methodNode)
        //返回节点
        return methodNode
        //return super.visitMethod(access, name, descriptor, signature, exceptions)
    }

    override fun visitEnd() {
        if (cv != null) {
            cv.visitEnd()
        }
        //必须执行，否则打包失败
        accept(nextVisitor)
        //标记目标类
        markTargetClzByAno()
        //保存节点到缓存池
        if(needSaveToPool){
            AsmMockerCache.getInstance().saveClassVisitor(name, this)
        }
    }

    //标记目标类
    private fun markTargetClzByAno(){
        if(isMocker() && !getTargetClass().isNullOrEmpty()){
            AsmMockerCache.getInstance().markTargetClass(targetClass!!, this)
        }
        //保存method注释mocker信息
        methods?.forEach {
            if(it is AnMockerMethodNode && it.isMethodMocker()){
                println("add AnMockerMethodNode[${it.name}] in $name")
                methodMockers.add(it)
            }
        }
    }

    public fun getMethodMockers(): ArrayList<AnMockerMethodNode> {
        return methodMockers
    }

    public fun isMocker(): Boolean {
        return isClassMocker
    }

    public fun getTargetClass() : String?{
        return targetClass
    }
}